﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.IO;
using Tesla.Content.Loaders.Model.Dxs;
using Tesla.Core;
using Tesla.Graphics;
using Tesla.Math;
using Tesla.Scene;

namespace Tesla.Content.Loaders.Model {
    /// <summary>
    /// DXS Model loader. DXS files are XML files from the DeleD 3D Editor and can be
    /// individual models or an entire scene. Lights and animation data are not currently imported.
    /// 
    /// If the model is to use normal maps, then the DeleD Material must have the diffuse map or diffuse color
    /// set to the first layer, the normal map set to the second layer, and optionally a specular map
    /// set to the third layer. When a BlendType.Modulate is used, an alpha of .5f will be used.
    /// 
    /// Otherwise the standard effects are used. The first layer denotes either diffuse color (and alpha) or a diffuse texture,
    /// and the blendstate that will be used. The second layer may only be a diffuse texture. When BlendType.Modulate
    /// is used, an alpha of .5f will be set.
    /// 
    /// </summary>
    public class DXSLoader : ResourceLoader<Spatial> {
        private DxsParser _parser;
        private ModelLoaderParameters _defaultParams;

        /// <summary>
        /// Creates a new instance of DXSLoader.
        /// </summary>
        public DXSLoader() : base(".dxs") {
            _parser = new DxsParser();
            _defaultParams = new ModelLoaderParameters();
        }

        /// <summary>
        /// Load the DXS model. If the model is a single mesh, Mesh will
        /// be returned. Otherwise Node will be returned.
        /// </summary>
        /// <param name="resource">Dxs resource</param>
        /// <param name="parameters">Model loader parameters, must be of type ModelLoaderParameters otherwise it's ignored.</param>
        /// <returns>The model</returns>
        public override Spatial Load(IResource resource, LoaderParameters parameters) {
            _parser.Reset();

            ModelLoaderParameters modelParams = parameters as ModelLoaderParameters;
            if(modelParams == null) {
                modelParams = _defaultParams;
            }

            DxsNode dxsModel;

            using(Stream stream = resource.OpenStream()) {
                dxsModel = _parser.Parse(stream, resource.Name);
            }

            Node root = new Node(dxsModel.Name);
            foreach(DxsNode node in dxsModel.Children) {
                ParseDxsTree(node, root, modelParams);
            }

            if(root.Count == 1 && root[0] is Mesh) {
                Spatial s = root[0];
                s.RemoveFromParent();
                return s;
            }

            return root;
        }

        private void ParseDxsTree(DxsNode dxsNode, Node parent, ModelLoaderParameters modelParams) {
            if(dxsNode is DxsPrimitive) {
                ParseDxsPrimitive(dxsNode as DxsPrimitive, parent, modelParams);
            } else {
                Node child = new Node(dxsNode.Name);
                parent.AddChild(child);
                foreach(DxsNode node in dxsNode.Children) {
                    ParseDxsTree(node, child, modelParams);
                }
            }
        }

        private Material BuildMaterial(DxsMaterial material, Mesh mesh, bool hasVertexColors, bool hasNormals, ModelLoaderParameters modelParams) {
            if(modelParams.UserMaterialFile != null) {
                return ContentManager.Load<Material>(modelParams.UserMaterialFile).Clone();
            }

            bool hasTexture = material.HasTexture;
            bool hasLightMap = material.HasLightMap;
            bool useNormalMap = (hasNormals == true) && (modelParams.GenerateTangentBasis == true) && (modelParams.PreferLitMaterials == true);

            Material mat;

            if(useNormalMap) {
                if(hasVertexColors) {
                    mat = ContentManager.Load<Material>("NormalMapVertColor.tem").Clone();
                } else {
                    mat = ContentManager.Load<Material>("NormalMap.tem").Clone();
                }

                int num = 0;
                foreach(DxsLayer layer in material.Layers) {
                    if(num == 0) {
                        if(layer.MaterialType == MaterialType.Texture) {
                            mat.SetParameter("DiffuseMap", LoadTexture(layer.TextureFile, modelParams));
                            mat.SetParameter("UseDiffuseMap", true);
                        } else if(layer.MaterialType == MaterialType.Color) {
                            mat.SetParameter("MatDiffuse", layer.Color.ToVector3());
                            mat.SetParameter("UseDiffuseMap", false);
                        }

                        //Set blendstate
                        switch(layer.Blend) {
                            case BlendType.Modulate:
                                mat.SetParameter("Alpha", .5f);
                                mat.SetRenderState(BlendState.AdditiveBlend);
                                mesh.SceneHints.RenderBucketType = RenderBucketType.Transparent;
                                break;
                            case BlendType.Add:
                                mat.SetRenderState(BlendState.AdditiveBlend);
                                mesh.SceneHints.RenderBucketType = RenderBucketType.Transparent;
                                break;
                            case BlendType.Alphablend:
                                mat.SetRenderState(BlendState.AlphaBlendNonPremultiplied);
                                mesh.SceneHints.RenderBucketType = RenderBucketType.Transparent;
                                break;
                        }
                    } else if(num == 1) {
                        if(layer.MaterialType == MaterialType.Texture) {
                            mat.SetParameter("NormalMap", LoadTexture(layer.TextureFile, modelParams));
                        } 
                    } else if(num == 2) {
                        if(layer.MaterialType == MaterialType.Texture) {
                            mat.SetParameter("SpecularMap", LoadTexture(layer.TextureFile, modelParams));
                            mat.SetParameter("UseSpecularMap", true);
                        } else {
                            mat.SetParameter("UseSpecularMap", false);
                        }
                        break;
                    }
                    num++;
                }

                return mat;
            }

            if(modelParams.PreferLitMaterials && hasNormals) {
                if(material.HasTexture) {
                    if(hasVertexColors) {
                        mat = ContentManager.Load<Material>("LitBasicVertColorTexture.tem").Clone();
                    } else {
                        mat = ContentManager.Load<Material>("LitBasicTexture.tem").Clone();
                    }
                } else {
                    if(hasVertexColors) {
                        mat = ContentManager.Load<Material>("LitBasicVertColor.tem").Clone();
                    } else {
                        mat = ContentManager.Load<Material>("LitBasicColor.tem").Clone();
                    }
                }

                int num = 0;
                foreach(DxsLayer layer in material.Layers) {
                    if(num == 0) {
                        if(layer.MaterialType == MaterialType.Texture) {
                            mat.SetParameter("DiffuseMap", LoadTexture(layer.TextureFile, modelParams));
                        } else if(layer.MaterialType == MaterialType.Color) {
                            mat.SetParameter("MatDiffuse", layer.Color.ToVector3());
                        }

                        //Set blendstate
                        switch(layer.Blend) {
                            case BlendType.Modulate:
                                mat.SetParameter("Alpha", .5f);
                                mat.SetRenderState(BlendState.AdditiveBlend);
                                mesh.SceneHints.RenderBucketType = RenderBucketType.Transparent;
                                break;
                            case BlendType.Add:
                                mat.SetRenderState(BlendState.AdditiveBlend);
                                mesh.SceneHints.RenderBucketType = RenderBucketType.Transparent;
                                break;
                            case BlendType.Alphablend:
                                mat.SetRenderState(BlendState.AlphaBlendNonPremultiplied);
                                mesh.SceneHints.RenderBucketType = RenderBucketType.Transparent;
                                break;
                        }
                    } else if(num == 1) {
                        if(layer.MaterialType == MaterialType.Texture) {
                            mat.SetParameter("DiffuseMap", LoadTexture(layer.TextureFile, modelParams));
                        }
                        break;
                    }
                    num++;
                }
            } else {
                if(material.HasTexture) {
                    if(hasVertexColors) {
                        mat = ContentManager.Load<Material>("BasicVertColorTexture.tem").Clone();
                    } else {
                        mat = ContentManager.Load<Material>("BasicTexture.tem").Clone();
                    }
                } else {
                    if(hasVertexColors) {
                        mat = ContentManager.Load<Material>("BasicVertColor.tem").Clone();
                    } else {
                        mat = ContentManager.Load<Material>("BasicColor.tem").Clone();
                    }
                }

                int num = 0;
                foreach(DxsLayer layer in material.Layers) {
                    if(num == 0) {
                        if(layer.MaterialType == MaterialType.Texture) {
                            mat.SetParameter("DiffuseMap", LoadTexture(layer.TextureFile, modelParams));
                        } else {
                            if(layer.MaterialType == MaterialType.Color) {
                                mat.SetParameter("DiffuseColor", layer.Color.ToVector4());
                            }
                        }

                        //Set blendstate
                        switch(layer.Blend) {
                            case BlendType.Modulate:
                                if(layer.MaterialType == MaterialType.Color) {
                                    mat.SetParameter("DiffuseColor", new Color(layer.Color.ToVector3(), .5f).ToVector4());
                                }
                                mat.SetRenderState(BlendState.AdditiveBlend);
                                break;
                            case BlendType.Add:
                                mat.SetRenderState(BlendState.AdditiveBlend);
                                break;
                            case BlendType.Alphablend:
                                mat.SetRenderState(BlendState.AlphaBlendNonPremultiplied);
                                break;
                        }
                    } else if(num == 1) {
                        if(layer.MaterialType == MaterialType.Texture) {
                            mat.SetParameter("DiffuseMap", LoadTexture(layer.TextureFile, modelParams));
                        }
                        break;
                    }
                    num++;
                }
            }

            return mat;
        }


        private Texture LoadTexture(String file, ModelLoaderParameters modelParams) {
            if(file == null) {
                return null;
            }
            try {
                if(modelParams.TexturePath != null) {
                    return ContentManager.Load<Texture>(Path.Combine(modelParams.TexturePath, file), modelParams.TextureParameters);
                } else {
                    return ContentManager.Load<Texture>(file, modelParams.TextureParameters);
                }
            } catch(Exception e) {
                Console.WriteLine(String.Format("Could not load {0}, loading default texture\n {1}", file, e.Message));
                return ContentManager.Load<Texture>("notexture.tebo");
            }
        }


        private void ParseDxsPrimitive(DxsPrimitive dxsPrim, Node parent, ModelLoaderParameters modelParams) {
            int k = 0;

            dxsPrim.CompileMeshes(modelParams.NormalGeneration, modelParams.CreaseAngle, modelParams.SwapWindingOrder);

            foreach(DxsMesh dxsMesh in dxsPrim.CompiledMeshes) {
                Mesh mesh;
                if(k != 0) {
                    mesh = new Mesh(dxsPrim.Name + k);
                } else {
                    mesh = new Mesh(dxsPrim.Name);
                }

                //We transform the vertices to be centered around the origin, rotation/scale can't be determined from DeleD so
                //only care about translation
                mesh.Translation = dxsMesh.Translation;

                MeshData md = mesh.MeshData;

                DxsMaterial material = dxsPrim.Materials[dxsMesh.MaterialID];

                int vertCount = dxsMesh.Vertices.Count;

                DataBuffer<Vector3> positions = new DataBuffer<Vector3>(vertCount);
                DataBuffer<Vector2> texCoords = null;
                DataBuffer<Vector2> lightMapCoords = null;
                DataBuffer<Color> colors = null;
                DataBuffer<Vector3> normals = null;
                if(material.HasTexture) {
                    texCoords = new DataBuffer<Vector2>(vertCount);
                }
                if(material.HasLightMap) {
                    lightMapCoords = new DataBuffer<Vector2>(vertCount);
                }
                if(dxsPrim.VertexColorEnabled) {
                    colors = new DataBuffer<Color>(vertCount);
                }
                if(modelParams.NormalGeneration != NormalGeneration.None) {
                    normals = new DataBuffer<Vector3>(vertCount);
                }

                foreach(DxsVertex v in dxsMesh.Vertices) {
                    positions.Set(v.Position);
                    if(texCoords != null) {
                        texCoords.Set(v.TextureCoordinate);
                    }
                    if(lightMapCoords != null) {
                        lightMapCoords.Set(v.LightMapCoordinate);
                    }
                    if(colors != null) {
                        colors.Set(v.VertexColor);
                    }
                    if(normals != null) {
                        normals.Set(v.Normal);
                    }
                }

                md.Positions = positions;
                md.Indices = dxsMesh.Indices;
                if(texCoords != null) {
                    md.TextureCoordinates = texCoords;
                }
                if(lightMapCoords != null) {
                    md.AddBuffer(VertexSemantic.TextureCoordinate, 1, lightMapCoords);
                }
                if(colors != null) {
                    md.Colors = colors;
                }
                if(normals != null) {
                    md.Normals = normals;
                }

                //Build the material
                mesh.Material = BuildMaterial(material, mesh, colors != null, normals != null, modelParams);

                md.Reconstruct();

                if(modelParams.GenerateTangentBasis) {
                    mesh.ComputeTangentBasis();
                } 

                parent.AddChild(mesh);
            }
        }
    }
}
